﻿using System;
using IPCChannel;
using UProveCrypto;

namespace UProveAPI
{
    public class Issuer
    {
        private const string UIDP = "MyIssuer";
        private IIPCClient ipcClient;
        private UProveCrypto.Issuer issuer;
        private string privateKeyBase64;
        private byte[] tokenInformation;
        private IssuerKeyAndParameters issuerKeyParams;
        private string verifierIp;
        private int verifierPort;

        public Issuer(IIPCClient ipcClient, string verifierIp, int verifierPort)
        {
            this.ipcClient = ipcClient;
            ipcClient.MessageReceived += IpcClientOnMessageReceived;
            tokenInformation = ipcClient.Encoding().GetBytes("token information value");
            this.verifierIp = verifierIp;
            this.verifierPort = verifierPort;
        }

        public void Start()
        {
            ipcClient.StartListening();
        }

        private void IpcClientOnMessageReceived(IPCMessage message)
        {
            switch (message.MessageType)
            {
                case IPCMessageTypes.SetupMessage:
                    SetupMessage setupMessage = message as SetupMessage;
                    HandleSetupMessage(setupMessage);
                    break;
                case IPCMessageTypes.IssuanceMessage:
                    IssuanceMessage issuanceMsg = message as IssuanceMessage;
                    HandleIssuanceMessage(issuanceMsg);
                    break;
                default:
                    break;
            }
        }

        private void HandleSetupMessage(SetupMessage setupMsg)
        {
            switch (setupMsg.DataType)
            {
                case SetupDataType.InitializeSetup:
                    Console.WriteLine("Received a setup command for " + setupMsg.AttributeCount + " attributes");

                    issuerKeyParams = SetupIssuer(setupMsg.AttributeCount);
                    privateKeyBase64 = issuerKeyParams.PrivateKey.ToBase64String();

                    SetupMessage setupMessage = new SetupMessage(SetupDataType.SetupFinished);
                    setupMessage.IssuerParamsJSON = issuerKeyParams.IssuerParameters.Serialize();
                    ipcClient.Send(setupMsg.SenderIp, setupMsg.SenderPort, setupMessage);
                    Console.WriteLine("Sent Issuer Parameters to Prover");

                    setupMessage.DataType = SetupDataType.VerifyParameters;
                    ipcClient.Send(verifierIp, verifierPort, setupMessage);
                    Console.WriteLine("Sent Issuer Parameters to Verifier\n");
                    break;
                default:
                    break;
            }
        }

        private void HandleIssuanceMessage(IssuanceMessage issuanceMsg)
        {
            switch (issuanceMsg.DataType)
            {
                case IssuanceDataType.InitalizeIssuance:
                    Console.WriteLine("Received Issuance initialization message from Prover");

                    IssuanceMessage firstMessage = new IssuanceMessage(IssuanceDataType.FirstIssuance);
                    firstMessage.TokenInformation = tokenInformation;
                    firstMessage.JSONData = IssuanceFirstMessage(issuanceMsg.Attributes, issuanceMsg.TokenCount, issuanceMsg.ProverInformation);
                    ipcClient.Send(issuanceMsg.SenderIp, issuanceMsg.SenderPort, firstMessage);

                    Console.WriteLine("Sent first Issuance message to Prover");
                    break;
                case IssuanceDataType.SecondIssuance:
                    Console.WriteLine("Received second Issuance message from Prover");

                    IssuanceMessage secondMessage = new IssuanceMessage(IssuanceDataType.FinalIssuance);
                    SecondIssuanceMessage msgObj = issuerKeyParams.IssuerParameters.Deserialize<SecondIssuanceMessage>(issuanceMsg.JSONData);
                    secondMessage.JSONData = issuerKeyParams.IssuerParameters.Serialize<ThirdIssuanceMessage>(issuer.GenerateThirdMessage(msgObj));
                    ipcClient.Send(issuanceMsg.SenderIp, issuanceMsg.SenderPort, secondMessage);

                    Console.WriteLine("Sent third Issuance message to Prover\n");
                    break;
                default:
                    break;
            }
        }

        private IssuerKeyAndParameters SetupIssuer(int numberOfAttributes, GroupType groupType = GroupType.Subgroup, bool supportDevice = false)
        {
            IssuerSetupParameters isp = new IssuerSetupParameters();

            // Pick a unique identifier for the issuer params
            isp.UidP = ipcClient.Encoding().GetBytes(UIDP);

            // Set the number of attributes in the U-Prove tokens
            isp.NumberOfAttributes = numberOfAttributes;

            // An application profile would define the format of the specification field,
            isp.S = ipcClient.Encoding().GetBytes("application-specific specification");

            // Specify the group type: subgroup (default) or ECC
            isp.GroupConstruction = groupType;

            return isp.Generate(supportDevice);
        }

        private string IssuanceFirstMessage(byte[][] attributes, int tokens, byte[] proverInfo)
        {
            // Setup the issuer and generate the first issuance message
            IssuerProtocolParameters ipp = new IssuerProtocolParameters(issuerKeyParams);
            ipp.Attributes = attributes;
            ipp.NumberOfTokens = tokens;
            ipp.TokenInformation = tokenInformation;
            issuer = ipp.CreateIssuer();

            return issuerKeyParams.IssuerParameters.Serialize<FirstIssuanceMessage>(issuer.GenerateFirstMessage());
        }
    }
}
